package compute

import (
	"errors"
	"go/scanner"
	"go/token"
	"strings"

	"github.com/alfredxing/calc/constants"
	"github.com/alfredxing/calc/operators/functions"
)

type Computer struct {
	err   error              //错误信息
	List  map[string]float64 //变量关联
	Valid bool               //表达式是否有效
	scer  scanner.Scanner
}

//新建一个计算表达式
func NewComputer(calc string) *Computer {
	c := new(Computer)
	if calc == "" {
		c.err = errors.New("No expression.")
	} else {
		c.List = map[string]float64{}
		c.scer = initScanner(calc)
		c.err = c.PreAnalysis()
		if c.err == nil {
			c.Valid = true
		}
	}
	return c
}

//获取最后一个错误
func (this *Computer) GetLastError() (err error) {
	return this.err
}

//预分析处理表达式 (解析出相关的变量)
func (this *Computer) PreAnalysis() (err error) {
	s := this.scer
ScanLoop:
	for {
		_, tok, lit := s.Scan()
		switch {
		case tok == token.EOF:
			break ScanLoop
		case constants.IsConstant(lit):
		case isOperand(tok):
			_, err := parseFloat(lit)
			if err != nil {
				this.err = err
				return err
			}
		case functions.IsFunction(lit):
		case isOperator(tok.String()):
		case tok == token.LPAREN:
		case tok == token.RPAREN:
		case tok == token.SEMICOLON:
		case tok == token.IDENT:
			this.List[lit] = 0
		default:
			inspect := tok.String()
			if strings.TrimSpace(lit) != "" {
				inspect += " (`" + lit + "`)"
			}
			err = errors.New("Unrecognized token " + inspect + " in expression")
			this.err = err
			return err
		}
	}
	return
}

//设置变量参数
func (this *Computer) SetParamter(name string, value float64) {
	this.List[name] = value
}

//计算
func (this *Computer) Calc() (value float64, err error) {
	floats := NewFloatStack()
	ops := NewStringStack()
	s := this.scer

	var prev token.Token = token.ILLEGAL

ScanLoop:
	for {
		_, tok, lit := s.Scan()
		//log.Println(tok, lit)
		switch {
		case tok == token.EOF:
			break ScanLoop
		case constants.IsConstant(lit):
			floats.Push(constants.GetValue(lit))
			if prev == token.RPAREN || isOperand(prev) {
				evalUnprecedenced("*", ops, floats)
			}
		case isOperand(tok):
			val, err := parseFloat(lit)
			if err != nil {
				return 0, err
			}
			floats.Push(val)
			if prev == token.RPAREN || constants.IsConstant(prev.String()) {
				evalUnprecedenced("*", ops, floats)
			}
		case functions.IsFunction(lit):
			if isOperand(prev) || prev == token.RPAREN {
				evalUnprecedenced("*", ops, floats)
			}
			ops.Push(lit)
		case isOperator(tok.String()):
			op := tok.String()
			if isNegation(tok, prev) {
				op = "neg"
			}
			evalUnprecedenced(op, ops, floats)
		case tok == token.LPAREN:
			if isOperand(prev) {
				evalUnprecedenced("*", ops, floats)
			}
			ops.Push(tok.String())
		case tok == token.RPAREN:
			for ops.Pos >= 0 && ops.SafeTop() != "(" {
				err := evalOp(ops.SafePop(), floats)
				if err != nil {
					return 0, err
				}
			}
			_, err := ops.Pop()
			if err != nil {
				return 0, errors.New("Can't find matching parenthesis!")
			}
			if ops.Pos >= 0 {
				if functions.IsFunction(ops.SafeTop()) {
					err := evalOp(ops.SafePop(), floats)
					if err != nil {
						return 0, err
					}
				}
			}
		case tok == token.SEMICOLON:
		case tok == token.IDENT:
			floats.Push(this.List[lit])
			if prev == token.RPAREN || constants.IsConstant(prev.String()) {
				evalUnprecedenced("*", ops, floats)
			}

		default:
			inspect := tok.String()
			if strings.TrimSpace(lit) != "" {
				inspect += " (`" + lit + "`)"
			}
			return 0, errors.New("Unrecognized token " + inspect + " in expression")
		}
		prev = tok
	}

	for ops.Pos >= 0 {
		op, _ := ops.Pop()
		err := evalOp(op, floats)
		if err != nil {
			return 0, err
		}
	}

	res, err := floats.Top()
	if err != nil {
		return 0, errors.New("Expression could not be parsed!")
	}
	//log.Println("value:", res)
	return res, nil
}
